#include "common.h"
#include "em_cmu.h"
#include "em_gpio.h"

#include "elab_pin.h"
#include "bsp_pin.h"
#include "bsp_pin.h"

#define TAG "BSP_74HC595"

static elab_err_t _init(elab_pin_t * const me);
static elab_err_t _set_mode(elab_pin_t * const me, uint8_t mode);
static elab_err_t _get_status(elab_pin_t * const me, bool *status);
static elab_err_t _set_status(elab_pin_t * const me, bool status);

static  const elab_pin_ops_t hc595_pin_driver_ops = {
    .init = _init,
    .set_mode = _set_mode,
    .get_status = _get_status,
    .set_status = _set_status,
};

static elab_pin_driver_t shcp_pin;
static elab_pin_driver_t stcp_pin;
static elab_pin_driver_t oe_pin;
static elab_pin_driver_t ds_pin;

typedef struct hc595_pin_s{
    union 
    {
        struct {
            uint8_t pin0:1;
            uint8_t pin1:1;
            uint8_t pin2:1;
            uint8_t pin3:1;
            uint8_t pin4:1;
            uint8_t pin5:1;
            uint8_t pin6:1;
            uint8_t pin7:1;
        }pin;
        uint8_t port;
    };
}hc595_pin_t ;


struct hc595{
    elab_device_t* shcp;
    elab_device_t* stcp;
    elab_device_t* oe;
    elab_device_t* ds;
    hc595_pin_t status;
}hc595_oper;


void bsp_74hc595_flush_byte(uint8_t port)
{
    elab_pin_set_status(hc595_oper.shcp, 0);
    elab_pin_set_status(hc595_oper.stcp, 0);
    elab_pin_set_status(hc595_oper.oe, 0);
    elab_pin_set_status(hc595_oper.ds, 0);

    for(int i = 0; i < 8; i++) {
        uint8_t bit = ((port << i) & 0x80);
        elab_pin_set_status(hc595_oper.ds, bit);
        delay_us(10);
        elab_pin_set_status(hc595_oper.shcp, 1);
        delay_us(10);
        elab_pin_set_status(hc595_oper.shcp, 0);
    }

    // elab_pin_set_status(hc595_oper.stcp, 0);
    // delay_us(10);
    elab_pin_set_status(hc595_oper.stcp, 1);
    delay_us(10);
    elab_pin_set_status(hc595_oper.stcp, 0);
    elab_pin_set_status(hc595_oper.oe, 1);
}



void bsp_74hc595_init(void)
{
    bsp_gpio_pin_init(&shcp_pin, "595_shcp", "B.14");
    bsp_gpio_pin_init(&stcp_pin, "595_stcp", "B.15");
    bsp_gpio_pin_init(&oe_pin, "595_oe", "B.12");
    bsp_gpio_pin_init(&ds_pin, "595_ds", "B.11");

    hc595_oper.shcp = device_find("595_shcp");
    hc595_oper.stcp = device_find("595_stcp");
    hc595_oper.oe = device_find("595_oe");
    hc595_oper.ds = device_find("595_ds");

    elab_pin_set_mode(hc595_oper.shcp, PIN_MODE_OUTPUT_PP);
    elab_pin_set_mode(hc595_oper.stcp, PIN_MODE_OUTPUT_PP);
    elab_pin_set_mode(hc595_oper.oe, PIN_MODE_OUTPUT_PP);
    elab_pin_set_mode(hc595_oper.ds, PIN_MODE_OUTPUT_PP);
    elab_pin_set_status(hc595_oper.oe, 1);
    hc595_oper.status.port = 0;
    bsp_74hc595_flush_byte(hc595_oper.status.port);
}

INIT_EXPORT(bsp_74hc595_init, EXPORT_HC595);






bool hc595_check_pin_name_valid(const char *name)
{
    bool valid = true;

    if ((strlen(name) != 8) || name[5] != '.')
    {
        valid = false;
    }
    else if (!(name[6] >= '0' && name[6] <= '9'))
    {
        valid = false;
    }
    else if (!(name[7] >= '0' && name[7] <= '9'))
    {
        valid = false;
    }
    else if(memcmp(name, "HC595", strlen("HC595")))
    {
        valid = false;
    }

    char *str_num = (char *)&name[6];
    int32_t pin_num = atoi(str_num);
    if (pin_num < 0 || pin_num >= 8)
    {
        valid = false;
    }
    return valid;
}

static uint16_t hc595_get_pin_from_name(const char *name)
{
    char *str_num = (char *)&name[6];
    int32_t pin_num = atoi(str_num);
    return (uint16_t)(pin_num);
}



int bsp_hc595_gpio_pin_init(elab_pin_driver_t *me, const char * name, const char* pin_name)
{

    if(!hc595_check_pin_name_valid(pin_name)) return -1;

    memset(&me->device.super, 0, sizeof(me->device.super));
    
    me->pin_name = pin_name;

    elab_pin_register(&me->device, name, &hc595_pin_driver_ops, me);

    return 0;
}

/* private functions -------------------------------------------------------- */
/**
  * @brief  The PIN driver initialization function.
  * @param  me  PIN device handle.
  * @retval None.
  */
static elab_err_t _init(elab_pin_t * const me)
{
  (void) me;
    return ELAB_OK;
}


/**
  * @brief  The PIN driver set_mode function.
  * @param  me  PIN device handle.
  * @retval None.
  */
static elab_err_t _set_mode(elab_pin_t * const me, uint8_t mode)
{
  (void) me;
    return ELAB_OK;
}

/**
  * @brief  The PIN driver set_mode function.
  * @param  me  PIN device handle.
  * @retval GPIO status.
  */
static elab_err_t _get_status(elab_pin_t * const me, bool *status)
{
    elab_assert(me != NULL);

    elab_pin_driver_t *driver = (elab_pin_driver_t *)me->super.user_data;
    uint16_t pin = hc595_get_pin_from_name(driver->pin_name);

    int gpip_status = (hc595_oper.status.port >> pin) & 0x01; 
    *status = (gpip_status == 1) ? true : false;
    
    return ELAB_OK;
}

/**
  * @brief  The PIN driver set_status function.
  * @param  me      PIN device handle.
  * @param  status  GPIO status.
  * @retval None.
  */
static elab_err_t _set_status(elab_pin_t * const me, bool status)
{
    elab_assert(me != NULL);
    
    elab_pin_driver_t *driver = (elab_pin_driver_t *)me->super.user_data;
    uint16_t pin = hc595_get_pin_from_name(driver->pin_name);
    
    if(status)
    {
        hc595_oper.status.port |=  (1 << pin);
    }
    else
    {
        hc595_oper.status.port &=  ~(1 << pin);
    }
    bsp_74hc595_flush_byte(hc595_oper.status.port);
    return ELAB_OK;
}





